class System {
    canvascontext;
    canvaselement;

    gametick: number;
    frameID: number;
    dt: number;
    now: number;
    last: number;
    interpolationvalue: number;
    intervalID: number;
    lastinterpval;

    inputmanager;
    background;
    map;
    entities;
    snowball;
    hikerkills: number;
    hikervictories: number;

    state;

    soundmanager: SoundManager;
    ui: UI;
    yeti: Yeti;
    hikermanager: HikerManager;
    canvas;

    constructor(updateFPS, canvasID) {
        this.canvascontext = null;
        this.canvaselement = null;

        this.gametick = 0;
        this.frameID = 0;
        this.dt = 0; //time since last update
        this.now = 0;
        this.last = 0;
        this.interpolationvalue = 0; //value for entities to interpolate drawing with

        //used for canceling timers/requests
        this.frameID = 0;
        this.intervalID = 0;

        this.inputmanager = null;
        this.background = null;
        this.map = null;
        this.entities = [];
        this.snowball = null;
        this.hikerkills = 0;
        this.hikervictories = 0;

        this.state = 'none';
        this.init(updateFPS, canvasID);
    }
    //Main Game Loop
    init(updateFPS, canvasID) {
        //grab canvas element and context for later
        this.canvaselement = document.getElementById(canvasID);
        this.canvascontext = this.canvaselement.getContext('2d');

        this.gametick = 1000 / updateFPS; //finds game tick in Milliseconds from FPS given
        this.gametick = parseFloat(this.gametick.toFixed(5)); //to fix float errors

        //create input manager
        this.inputmanager = new window.game.Input();

        //create sound manager
        var mute_option = (window.isMuted ? window.isMuted : false);
        this.soundmanager = new window.game.SoundManager(mute_option);

        //create UI
        this.ui = new window.game.UI();

        //create the map which is the level data(gravity, mountain characteristics)
        this.map = new window.game.map();

        //init hiker manager, which spawns the hikers periodically
        this.hikermanager = new window.game.HikerManager();

        //create entities (Order matters)
        this.yeti = this.spawnentity('Yeti'); //keep a pointer for easier lookup later
        this.spawnentity('Mountain');
        this.snowball = this.spawnentity('Snowball');

        //start game loop
        var that = this;
        this.intervalID = setInterval(function () { that.run() }, 1); //start the game loop

        this.soundmanager.preloadsounds("GLOBAL",
            [
                { name: "ATOM_POP", path: "media/Sounds/atompop.mp3", multichannel: true, volume: 0.2 },
                { name: "IMPACT_B", path: "media/Sounds/impact_b.mp3", multichannel: true, volume: 1 },
                { name: "ENTERING_LAIR", path: "media/Sounds/humansacrifice.mp3", multichannel: true, volume: 0.5, loop: true }
            ]);

        this.state = "maingame"; //put state into main game
    }

    run() {
        if (play_background) {
            window.game.system.soundmanager.play("ENTERING_LAIR");
            play_background = false;
        }
        //MAIN RUN LOOP
        //Run Step to decouple draw and update
        this.now = Date.now(); //Date.now returns milliseconds
        this.dt = this.dt + Math.min(this.now - this.last, this.gametick * 5); //cap change in time at 5 frames

        while (this.dt > this.gametick) {
            this.update();
            this.dt -= this.gametick;
        }

        var that = this;

        //DRAWING SECTON
        window.cancelAnimationFrame(this.frameID); //cancel request if hasnt been drawn yet

        if (window.game.isPaused === false) {
            this.frameID = window.requestAnimationFrame(function () {
                //calculates the interpolation value for the frame
                that.interpolationvalue = Math.min((that.dt + (Date.now() - that.now)) / that.gametick, 1);
                that.lastinterpval = that.interpolationvalue;

                that.draw();
            });
        }
        else {
            //if game is paused dont update interpolation value
            that.interpolationvalue = that.lastinterpval;
            that.draw();
        }

        this.last = Date.now();
    }


    //These are your main update and draw functions which call all child entities and managers
    update() {
        //simple state machine
        switch (this.state) {
            case 'maingame':
                this.map.update(); //update level map from input

                var deadentities = [];
                var entity = null;
                for (var i = 0; i < this.entities.length; i++) {
                    entity = this.entities[i];
                    if (entity.isDead === false) {
                        entity.update();
                    }
                    else {
                        deadentities[deadentities.length] = i;
                    }
                }

                this.hikermanager.update();
                this.ui.update();

                //clean up dead entities
                for (var i = 0; i < deadentities.length; i++) {
                    this.entities.splice(deadentities[i] - i, 1); //removes items from array
                }
                deadentities = null;

                this.inputmanager.update();
                break;
            case 'gameover':
                this.map.update(); //update level map from input

                var deadentities = [];
                var entity = null;
                for (var i = 0; i < this.entities.length; i++) {
                    entity = this.entities[i];
                    if (entity.isDead === false) {
                        entity.update();
                    }
                    else {
                        deadentities[deadentities.length] = i;
                    }
                }
                break;
            default:
                break;
        }
    }

    draw() {
        //simple state machine
        switch (this.state) {
            case 'maingame':
                this.canvascontext.clearRect(0, 0, 768, 1366);

                for (var i = 0; i < this.entities.length; i++) {
                    if (this.entities[i].isDead === false) {
                        this.entities[i].draw();
                    }
                }

                this.ui.draw();
                break;
            case 'gameover':
                this.canvascontext.clearRect(0, 0, 768, 1366);
                this.map.draw();  //shifts the canvas if needed

                for (var i = 0; i < this.entities.length; i++) {
                    if (this.entities[i].isDead === false) {
                        this.entities[i].draw();
                    }
                }

                this.ui.draw();
                break;
            default:
                break;
        }
    }

    unload() {
        //anything that needs to be cleared when game screen is left should be here.
        clearInterval(this.intervalID);
        document.getElementById('gameover_ui').className = 'hidden';
    }

    spawnentity(type, settings?) {
        //initialize child entities
        if (settings !== null && typeof (settings) !== undefined) {
            this.entities[this.entities.length] = new window.game[type](settings);
        }
        else {
            this.entities[this.entities.length] = new window.game[type]();
        }

        return this.entities[this.entities.length - 1]; //return object if caller wants to store it
    }

    hikersuccess() {
        //game is notified by hiker entities that one has reached the summit
        this.hikervictories++;

        if (this.hikervictories > 2) {
            //game over - setup game over state
            this.state = 'gameover';

            //set all current hikers to the game over state
            for (var i = 0; i < this.entities.length; i++) {
                if (this.entities[i].type === 'hiker') {
                    if (this.entities[i].state !== 'win') {
                        this.entities[i].state = 'gameover';
                    }
                    continue;
                }
                if (this.entities[i].type === 'snowball') {
                    this.entities[i].kill();
                }
            }

            this.yeti.changestate('rage'); //change yeti to angry state

            //show gameover DOM UI
            document.getElementById('gameover_ui').className = '';
        }
    }

    dominput = {
        //input commands called from the dom
        restart: function () {
            window.game.system.unload(); //clean up any game information
            window.game.system = new System(30, 'game_canvas');
        },
        exit: function () {

        },
        snowball: function (isDown) {
            if (isDown === true) {
                window.game.system.inputmanager.buttonstates.SNOWBALL_BUTTON = true;
            }
            else {
                window.game.system.inputmanager.buttonstates.SNOWBALL_BUTTON = false;
            }
        },
        left_btn: function (isDown) {
            if (isDown === true) {
                window.game.system.inputmanager.buttonstates.LEFT_BUTTON = true;
            }
            else {
                window.game.system.inputmanager.buttonstates.LEFT_BUTTON = false;
            }
        },
        right_btn: function (isDown) {
            if (isDown === true) {
                window.game.system.inputmanager.buttonstates.RIGHT_BUTTON = true;
            }
            else {
                window.game.system.inputmanager.buttonstates.RIGHT_BUTTON = false;
            }
        }

    };
} 